﻿//© 2010-2011 mOcean Mobile. A subsidiary of Mojiva, Inc. All Rights Reserved.

using System;
using System.IO.Ports;
using System.Text;
using System.Threading;

namespace mOceanWindows
{
    internal class Gps
    {
        private SerialPort _comport = null;
        private ManualResetEvent _stopEvent = new ManualResetEvent( false );
        private Thread _gpsEventThread = null;
        private string[] _gpsString;
        private string _instring;
        public bool Opened = false;
        public delegate void PositionReceivedEventHandler(double latitude,
          double longitude);
        #region Events
        public event PositionReceivedEventHandler PositionReceived;
        #endregion

        static String GetGpsDevice()
        {
            string[] ports = SerialPort.GetPortNames();

	        String port = String.Empty;
	        for (int f = 0; f < ports.Length; f++)
	        {
                using (SerialPort comport = new SerialPort(ports[f], 4800, Parity.None, 8, StopBits.One))
                {
                    try
                    {
                        comport.Open();
                        comport.ReadTimeout = 2000;
                        byte[] buffer = new byte[1024];
                        int nLen = comport.Read(buffer, 0, 1024);
                        if (nLen != 0)
                        {
                            ASCIIEncoding encoder = new ASCIIEncoding();
                            String data = encoder.GetString(buffer);

                            if (data.IndexOf("$GP", StringComparison.InvariantCultureIgnoreCase) != -1)
                            {
                                port = ports[f];
                                break;
                            }
                        }
                    }catch
                    {
                    }
                }
	        }

	        return port;
        }

        public void Open()
        {
            _stopEvent.Reset();
            // Create and start thread to listen for GPS events
            _gpsEventThread = new Thread(WaitForGpsEvents);
            _gpsEventThread.Start();
            
        }

        public void Close()
        {
            _stopEvent.Set();
        }

        private void WaitForGpsEvents()
        {
            lock (this)
            {
                Opened = true;
                string port = GetGpsDevice();

                if (!String.IsNullOrEmpty(port))
                {
                    _comport = new SerialPort(port, 4800, Parity.None, 8, StopBits.One);
                    _comport.DataReceived += port_DataReceived;
                    while (true)
                    {
                        if (_stopEvent.WaitOne(0)) break;

                    }
                    _comport.Close();
                    _comport.Dispose();
                }

                Opened = false;
                // clear our gpsEventThread so that we can recreate this thread again
                // if the events are hooked up again.
                _gpsEventThread = null;
            }
        }

        private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string inbuff = _comport.ReadExisting();
            if (inbuff != null)
            {
                if (inbuff.StartsWith("$"))
                {
                    _instring = inbuff;
                }
                else
                {
                    _instring += inbuff;
                }
                _gpsString = _instring.Split();
                foreach (string item in _gpsString) Parse(item);
            }
        }

        // Processes information from the GPS receiver
        public bool Parse(string sentence)
        {
            // Discard the sentence if its checksum does not match our
            // calculated checksum
            if (!IsValid(sentence)) return false;
            // Look at the first word to decide where to go next
            switch (GetWords(sentence)[0])
            {
                case "$GPRMC":
                    // A "Recommended Minimum" sentence was found!
                    return ParseGPRMC(sentence);
            }

            return false;
        }
        // Divides a sentence into individual words
        public string[] GetWords(string sentence)
        {
            //strip off the final * + checksum
            sentence = sentence.Substring(0, sentence.IndexOf("*"));
            //now split it up
            return sentence.Split(',');
        }

        // Interprets a $GPRMC message
        public bool ParseGPRMC(string sentence)
        {
            // Divide the sentence into words
            string[] Words = GetWords(sentence);
            // Do we have enough values to describe our location?
            if (Words[3] != "" & Words[4] != "" &
              Words[5] != "" & Words[6] != "")
            {
                // Yes. Extract latitude and longitude
                // Append hours
                string Latitude = Words[3].Substring(0, 2) + "°";
                // Append minutes
                Latitude = Latitude + Words[3].Substring(2) + "\"";
                // Append hours
                Latitude = Latitude + Words[4]; // Append the hemisphere
                string Longitude = Words[5].Substring(0, 3) + "°";
                // Append minutes
                Longitude = Longitude + Words[5].Substring(3) + "\"";
                // Append the hemisphere
                Longitude = Longitude + Words[6];

                 //grab the bit up to the °
                 double deciLat = Convert.ToDouble(Latitude.Substring(0, Latitude.IndexOf("°")));
                 double deciLon = Convert.ToDouble(Longitude.Substring(0, Longitude.IndexOf("°")));
                 
                 //remove that bit from the string now we've used it and the ° symbol
                 Latitude = Latitude.Substring(Latitude.IndexOf("°") + 1);
                 Longitude = Longitude.Substring(Longitude.IndexOf("°") + 1);
                 
                 //grab the bit up to the " - divide by 60 to convert to degrees and add it to our double value
                 deciLat += (Convert.ToDouble(Latitude.Substring(0, Latitude.IndexOf("\""))))/60;
                 deciLon += (Convert.ToDouble(Longitude.Substring(0, Longitude.IndexOf("\""))))/60;
                 
                 //ok remove that now and just leave the compass direction
                 Latitude = Latitude.Substring(Latitude.IndexOf("\"") + 1);
                 Longitude = Longitude.Substring(Longitude.IndexOf("\"") + 1);
              
                 // check for negative directions
                 if (Latitude == "S") deciLat = 0 - deciLat;
                 if (Longitude == "W") deciLon = 0 - deciLon;

                // Notify the calling application of the change
                if (PositionReceived != null)
                    PositionReceived(deciLat, deciLon);
            }
            // Indicate that the sentence was recognized
            return true;
        }

        // calculated checksum
        private static bool IsValid(string sentence)
        {
            // Compare the characters after the asterisk to the calculation
            return sentence.Substring(sentence.IndexOf("*") + 1) ==
              GetChecksum(sentence);
        }
        // Calculates the checksum for a sentence
        private static string GetChecksum(string sentence)
        {
            // Loop through all chars to get a checksum
            int Checksum = 0;
            foreach (char Character in sentence)
            {
                if (Character == '$')
                {
                    // Ignore the dollar sign
                }
                else if (Character == '*')
                {
                    // Stop processing before the asterisk
                    break;
                }
                else
                {
                    // Is this the first value for the checksum?
                    if (Checksum == 0)
                    {
                        // Yes. Set the checksum to the value
                        Checksum = Convert.ToByte(Character);
                    }
                    else
                    {
                        // No. XOR the checksum with this character's value
                        Checksum = Checksum ^ Convert.ToByte(Character);
                    }
                }
            }
            // Return the checksum formatted as a two-character hexadecimal
            return Checksum.ToString("X2");
        }


    }
}
